<?php

/**
 * Maikuro Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2010, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

/**
 * @see Utility
 */
require_once 'Utility.php';

/**
 *
 */
class Form {

    /**
     * The elements of the form with their names as keys and values as values.
     *
     * @var array
     */
    protected $elements = array();

    /**
     *
     *
     * @var array
     */
    protected $filters = array();

    /**
     * Indicates whether the form is valid or not. A null value is interpreted
     * as of no validation has been done.
     *
     * @var boolean
     */
    protected $valid;

    /**
     * Any element validators. Should be specified with the element as key and
     * validators as array value. An example:
     * 'myElement' => array(
     *      'required',
     *      'someOtherValidator' => array('someValidatorParam')
     * )
     *
     * @var array
     */
    protected $validators = array();

    /**
     * The validation results.
     *
     * @var array
     */
    protected $validationResults = array();

    /**
     * Any validation messages.
     *
     * @var array
     */
    protected $validationMessages = array();

    /**
     * Constructor.
     *
     * @return Form
     */
    public function __construct()
    {
        if (!Utility::isArrayAssociative($this->elements)) {
            $this->elements = array_fill_keys($this->elements, null);
        }
        return $this;
    }

    /**
     * "Magic" method to get a form element by trying to access it like a public
     * property, i.e. $this->myFormElement.
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name) {
        return $this->getData($name);
    }

    /**
     * "Magic" method to check if a form element is set by using the 'isset'
     * function.
     *
     * @param string $name
     * @return boolean
     */
    public function __isset($name) {
        return isset($this->elements[$name]);
    }

    /**
     * "Magic" method to set the value of a form element by accessing it like a
     * public property, i.e. $this->myFormElement = 'My value'.
     *
     * @param string $name
     * @param mixed $value
     * @return void
     */
    public function __set($name, $value) {
    	if (isset($this->elements[$name])) {
            $this->elements[$name] = $value;
    	} else {
            throw new Exception('No element "' . $name . '" exists.');
    	}
    }

    /**
     * "Magic" method to unset a form element using the 'unset' function.
     *
     * @param string $name
     * @return void
     */
    public function __unset($name) {
        unset($this->elements[$name]);
    }


    /**
     * Adds an element.
     *
     * @param string $element
     * @param array $filters
     * @param array $validators
     * @return Form
     */
    public function addElement($element, array $filters = null,
        array $validators = null)
    {
        $this->elements[$element] = null;

        if ($filters !== null) {
            $this->filters[$element] = $filters;
        }

        if ($validators !== null) {
            $this->validators[$element] = $validators;
        }

        return $this;
    }

    /**
     * Adds multiple elements.
     *
     * @param array $elements
     * @return Form
     */
    public function addElements(array $elements, array $filters = null,
        array $validators = null)
    {
        $this->elements = array_merge($this->elements, $elements);

        if ($filters !== null) {
            $this->filters = array_merge($this->filters, $filters);
        }

        if ($validators !== null) {
            $this->validators = array_merge($this->validators, $validators);
        }

        return $this;
    }

    /**
     * Adds a filter for $element.
     *
     * @param string $element
     * @param string|array $filter
     * @return Form
     */
    public function addFilter($element, $filter, array $filterParams = null)
    {
        if (!is_array($this->filters[$element])) {
            $this->filters[$element] = array();
        }

        if ($filterParams !== null) {
            $this->filters[$element][$filter] = $filterParams;
        } else {
            $this->filters[$element][] = $filter;
        }

        return $this;
    }

    /**
     * Adds a validator for $element.
     *
     * @param string $element
     * @param string|array $validator
     * @return Form
     */
    public function addValidator($element, $validator,
        array $validatorParams = null)
    {
        if (!is_array($this->validators[$element])) {
            $this->validators[$element] = array();
        }

        if ($validatorParams !== null) {
            $this->validators[$element][$validator] = $validatorParams;
        } else {
            $this->validators[$element][] = $validator;
        }

        return $this;
    }

    /**
     * Clears all element data.
     *
     * @return Form
     */
    public function clearData()
    {
        foreach ($this->elements as $element => $value) {
            $this->elements[$element] = null;
        }
        return $this;
    }

    /**
     * Gets the elements with their data. If an $elementNames array is
     * supplied, only the chosen elements will be returned.
     *
     * @param array $elementNames
     * @return array
     */
    public function getData($elementNames = null) {
        if (is_array($elementNames)) {
            $elements = array();
            foreach ($elementNames as $elementName) {
                $elements[$elementName] = $this->elements[$elementName];
            }
            return $elements;
        } else if ($elementNames !== null) {
            return $this->elements[$elementNames];
        }
        return $this->elements;
    }

    /**
     * Returns any validation messages for the whole form or a single element.
     *
     * @param string $element
     * @return array
     */
    public function getValidationMessages($element = null)
    {
        if ($element !== null) {
            if (!isset($this->validationMessages[$element])) {
                return null;
            }
            return $this->validationMessages[$element];
        }
        return $this->validationMessages;
    }

    /**
     * Checks if a single element is valid or not. The element will be validated
     * if no validation has been made before.
     *
     * @param string $element
     * @return boolean
     */
    public function isElementValid($element)
    {
        if (!isset($this->validationResults[$element])) {
            $this->validateElement($element);
        }
        return $this->validationResults[$element];
    }

    /**
     * Checks whether the form is valid or not. If the form isn't validated,
     * a validation will be made.
     *
     * @return boolean
     */
    public function isValid()
    {
        if ($this->valid === null) {
            $this->validate();
        }
        return $this->valid;
    }

    /**
     * Removes an element.
     *
     * @param string $elementName
     * @return void
     */
    public function removeElement($elementName)
    {
        unset($this->elements[$elementName]);
        unset($this->filters[$elementName]);
        unset($this->validators[$elementName]);
    }

    /**
     * Sets values to the elements added to this form.
     *
     * @param array $data
     * @return Form
     */
    public function setData(array $data)
    {
        Utility::filterArray($data, $this->filters);
        $this->elements = Utility::arrayOverlay($this->elements, $data);
        return $this;
    }

    /**
     * Validates the form.
     *
     * @return Form
     */
    public function validate()
    {
        $valid = true;
        foreach ($this->elements as $element => $elementValue) {
            $result = $this->validateElement($element);
            if (!$result) {
                $valid = false;
            }
        }
        $this->valid = $valid;
        return $this;
    }

    /**
     * Validates a single element.
     *
     * @param string $element
     * @return boolean
     */
    public function validateElement($element)
    {
        $elementValue = $this->elements[$element];
        if (!isset($this->validators[$element])) {
            $this->validationResults[$element] = true;
            return true;
        }

        require_once 'Validators.php';

        $valid = true;
        foreach ($this->validators[$element] as $key => $value) {
            if (is_int($key)) {
                $validator = $value;
                $result = Validators::$validator($elementValue);
            } else if (is_array($value)) {
                $validator = $key;
                $validatorParams = array_unshift($value, $elementValue);
                $result = call_user_func_array(array('Validators', $validator), $validatorParams);
            } else {
                $validator = $key;
                $result = Validators::$validator($elementValue, $value);
            }
            if (!$result) {
                $valid = false;
            }
        }

        $this->validationResults[$element] = $valid;
        $this->validationMessages[$element] = Validators::getMessages();

        return $valid;
    }

}